package com.bestcem.xm.user.config.aspects;

import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.domain.web.XmResultJson;
import com.bestcem.xm.common.core.enums.ResourceErrorCodeEnum;
import com.bestcem.xm.user.annotations.FreqLimit;
import com.bestcem.xm.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;

/**
 * 限制访问频率切面拦截
 *
 * @author panyun <yun.pan@idiaoyan.com>
 * @version v1.0
 * @date 2022/7/14 16:35
 */
@Aspect
@Component
@Slf4j
public class FreqLimitAspect {

    @Resource
    private UserService userService;

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.bestcem.xm.user.annotations.FreqLimit)")
    public void freqLimitPointCut() {
        // Do nothing because of pointcut expression
    }

    /**
     * 切面请求频率限制
     *
     * @author panyun <yun.pan@idiaoyan.com>
     * @date 2022/7/14 16:39
     */
    @Around("freqLimitPointCut()")
    public Object freqLimit(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        FreqLimit freqLimit = method.getAnnotation(FreqLimit.class);
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        int durian = freqLimit.durian();
        int limit = freqLimit.limit();
        boolean url = freqLimit.url();
        boolean withMethod = freqLimit.withMethod();
        String[] jsonKeys = freqLimit.jsonKeys();
        String[] jwtKeys = freqLimit.jwtKeys();
        String multipleRejectionPrefix = freqLimit.multipleRejectionPrefix();

        ArrayList<String> res = new ArrayList<>();
        if (StringUtils.isNotBlank(multipleRejectionPrefix)) {
            res.add(multipleRejectionPrefix);
        }
        if (url && Objects.nonNull(requestAttributes)) {
            res.add(requestAttributes.getRequest().getRequestURI());
        } else {
            String className = pjp.getTarget().getClass().getName();
            res.add(className);
        }
        if (jsonKeys.length != 0) {
            Object[] args = pjp.getArgs();
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] parameterNames = u.getParameterNames(signature.getMethod());

            HashMap<String, Object> map = new HashMap<>(16);
            for (int i = 0; i < args.length; i++) {
                if (Objects.nonNull(parameterNames)) {
                    map.put(parameterNames[i], args[i]);
                }
            }
            for (String jsonKey : jsonKeys) {
                Object val = map.get(jsonKey);
                res.add(Objects.nonNull(val) ? val.toString() : "");
            }
            res.addAll(Arrays.asList(jsonKeys));
        }
        if (jwtKeys.length != 0) {
            res.addAll(Arrays.asList(jwtKeys));
        }
        if (withMethod) {
            String methodName = pjp.getSignature().getName();
            res.add(methodName);
        }

        ServiceResult<Boolean> checkFrequency = userService.checkFrequency(String.join(".", res), durian, limit);
        if (!checkFrequency.isSuccess()) {
            return XmResultJson.fail(ResourceErrorCodeEnum.TOO_BUSY, "请求过于频繁");
        }

        try {
            return pjp.proceed();
        } catch (Throwable e) {
            log.error("频率限制业务异常", e);
            return XmResultJson.fail(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "系统异常");
        }
    }
}
